# LIBRARIES
library(tidyverse) #Set of packages that work in harmony for data representations and API design (ggplot2, dplyr, readr, etc)
library(sf) #Package for Simple Features, way to encode spatial vector data. Binds to GDAL for reading/writing of data, GEOS for geometrical operations and PROJ for projection conversions/datum transformations (http://strimas.com/r/tidy-sf/)
library(tmap)
library(USAboundaries) #Boundaries for geographical units in the United States of America (U.S. Census Bureau)
library(tidyr) #Use to reshape dataframe
library(lubridate) #Handle dates/time series
library(forcats) #Handle factors
### MONITORING SITES LOCATIONS
#Absolute path of the MS locations in the FTP server
locfile <- 'Data/AirNow/monitoring_site_locations.dat'
#Metadata on MonitoringSiteFactSheet.pdf
fieldnames <- c("AQSID","parameter.name","site.code","site.name","status","agency.id","agency.name","EPA.region","latitude","longitude","elevation","GMT.offset","country.code","CMSA.code","CMSA.name","MSA.code","MSA.name","state.code","state.name","county.code","county.name","city.code","city.name")
#Read the .dat file in a tibble dataframe. Tibbles are modern data frames that keep the useful features of the traditional data frames and drop ones that were frustating (e.g. character vectors to factor, show only columns that fit to the screen when printing, etc.)
MS <- read_delim(locfile, delim='|', col_names = fieldnames, progress = TRUE)
print(MS)
#List of parameters measured
MS.chems <- unique(MS$parameter.name)
print(paste('In total, there are',length(MS.chems),'parameters measured',sep=' '))
[1] "In total, there are 27 parameters measured"
print(MS.chems)
 [1] "O3"       "PM2.5"    "RWD"      "TEMP"     "RHUM"     "SO2"      "RWS"     
 [8] "BARPR"    "PM10"     "WS"       "WD"       "PRECIP"   "NOY"      "NO"      
[15] "SRAD"     "NOX"      "NO2"      "CO"       "NO2Y"     "BC"       "UV-AETH" 
[22] "PMC"      "H2S"      "DEWPOINT" "SO2-15"   "PM2.5-15" "SO4"     
#List of stations
MS.stat <- unique(MS$AQSID)
print(paste('In total, there are',length(MS.stat),'different stations',sep=' '))
[1] "In total, there are 2738 different stations"
#MS locations
#Extract stations measuring ozone (MS.chems=O3) and particulate matter (MS.chems=PM2.5)
MS <- filter(MS,parameter.name %in% c('PM2.5','O3'))
#Convert the data frame to a spatial data frame and project the coordinates in USA Contiguous Albers Equal Area Conic (EPSG:102003)
MS <- st_as_sf(MS, coords = c('longitude','latitude'), agr = 'identity', crs=4326) %>% st_transform(MS, crs=102003) 
#Visualisation of PM2.5 and O3 MS (different colors for active/inactive MS)
tmap_mode("view")
tm_shape(us_counties()) + tm_borders(col='black', lty=2) +
  tm_shape(us_states()) + tm_borders(col='black', lwd=2) +
  tm_shape(MS[MS$parameter.name=='PM2.5',],name='PM2.5 MS locations') +          tm_dots(col='status',palette=c('blue','red')) + 
  tm_shape(MS[MS$parameter.name=='O3',],name='O3 MS locations') + tm_dots(col='status',palette=c('blue','red'),legend.show=FALSE)
#CREATE TWO DATAFRAMES : counties to store all the counties we want to keep, MS to store all the monitoring stations belonging to the counties
#include neighboring counties of Chicago and also Lake and Porter from Indiana (lot of industries)
counties <- us_counties() %>% filter(state_name %in% c('Illinois','Indiana','Wisconsin') & name %in% c('Cook','Lake','DuPage','Will','Porter','Kenosha')) %>% st_transform(counties, crs=102003) 
#Sites measuring O3 and PM2.5 at the 6 counties we're interested in
MS <- MS[which(st_intersects(MS,counties) > 0),]
#Same MS for PM2.5 and O3
MS.both<-group_by(MS,by=AQSID) %>% summarise(length=n()) %>% filter(length==2) %>% .[['by']]
#Plot the counties included in the MS location
ggplot(counties,aes(fill = affgeoid)) + geom_sf()

#Add metadata about the Monitoring Stations (Type, Objectives, ...) from the different states documents (Illinois, Indiana, Wisconsin). Informations for Wisconsin and Indiana will be added manually (only a few stations)
path_docs <- '/Volumes/GoogleDrive/My Drive/PDM/Docs/'
MS <- read_csv(paste0(path_docs,'meta_O3_MS.csv'),col_names = TRUE,na = c("N/A", "NA")) %>% rbind(read_csv(paste0(path_docs,'meta_PM25_MS.csv'),col_names = TRUE,na = c("N/A", "NA"))) %>% mutate(AQSID = gsub(pattern='-',replacement = '',x=AQSID)) %>% merge(x=MS,by=c('AQSID','parameter.name'),all.x=TRUE)
MS <- MS %>% select(AQSID,site.name.x,parameter.name,status,state.name,county.name,primary.objective,secondary.objective,spatial.scale,station.type) %>% rename(site.name=site.name.x)
#READ DATA FOR ALL YEARS
yeardir <- 'Data/AirNow/DailyPeak'
yearfiles <- list.files(yeardir,full.names=TRUE)
fieldnames <- c("vdate","AQSID","site.name","parameter.name","report.units",
                "value","avg.period","agency.name")
#Function to extract the desired data (Ozone-8h and PM2.5-24h) for a given file
extract_data <- function(file) {
  return(read_delim(file, "|", col_names = fieldnames, progress = FALSE) %>% filter(AQSID %in% unique(MS$AQSID)) %>% filter(parameter.name=='OZONE-8HR' | parameter.name=='PM2.5-24hr'))
}
#For loop to go over all the files in the directory
data <- data.frame()
for(j in 1:length(yearfiles)) {
  datadir <- paste0(yeardir,substr(yearfiles[j],nchar(yearfiles[j])-4,nchar(yearfiles[j])))
  datfiles <- list.files(datadir,full.names=TRUE)
  
  for(i in 1:length(datfiles))
  {
  res <- extract_data(datfiles[i])
  data <- rbind(data,res)
  }
}
str(data)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   34181 obs. of  8 variables:
 $ vdate         : chr  "10/22/13" "10/22/13" "10/22/13" "10/22/13" ...
 $ AQSID         : chr  "170310001" "170310001" "170310032" "170310064" ...
 $ site.name     : chr  "ALSIP" "ALSIP" "CHI_SWFP" "CHI_U" ...
 $ parameter.name: chr  "PM2.5-24hr" "OZONE-8HR" "OZONE-8HR" "OZONE-8HR" ...
 $ report.units  : chr  "UG/M3" "PPB" "PPB" "PPB" ...
 $ value         : num  10.2 7 10 7 14 10 16 3.3 11 21 ...
 $ avg.period    : int  24 8 8 8 8 8 8 24 8 8 ...
 $ agency.name   : chr  "Illinois EPA" "Illinois EPA" "Illinois EPA" "Illinois EPA" ...
 - attr(*, "spec")=List of 2
  ..$ cols   :List of 8
  .. ..$ vdate         : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ AQSID         : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ site.name     : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ parameter.name: list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ report.units  : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  .. ..$ value         : list()
  .. .. ..- attr(*, "class")= chr  "collector_double" "collector"
  .. ..$ avg.period    : list()
  .. .. ..- attr(*, "class")= chr  "collector_integer" "collector"
  .. ..$ agency.name   : list()
  .. .. ..- attr(*, "class")= chr  "collector_character" "collector"
  ..$ default: list()
  .. ..- attr(*, "class")= chr  "collector_guess" "collector"
  ..- attr(*, "class")= chr "col_spec"
#DATA WRANGLING
data.bckp <- data
#data <- data.bckp
data_wrangling <- function(df) {
  #Keep only useful fields (time, AQSID, parameter, values)
  df <- select(df,c('vdate','AQSID','parameter.name','value')) 
  
  #Convert to tidy format (spread parameter name)
  df <- spread(df, key='parameter.name', value='value')
  
  #Change name of columns
  names(df) <- c('date','AQSID','O3','PM25')
  
  #Data conversion
  df$date<-mdy(df$date) #date from char to date
  df$AQSID<-factor(df$AQSID) #AQSID from char to factor
  return(df)
}
#Data wrangling process
data <- data_wrangling(data)
#Make appear missing dates explicitely using complete() from tidyr
data <- complete(data,date = seq.Date(min(date), max(date), by="day"))
#Print implicit missing values
missingD <- filter(data, is.na(O3) & is.na(PM25)) %>% .[['date']] %>% format("%Y%m%d")
print(missingD) #Dates with missing data
 [1] "20151230" "20170818" "20170819" "20170820" "20170821" "20170822" "20170823"
 [8] "20170824" "20170825" "20170826" "20170827" "20170828" "20170829" "20170830"
[15] "20170831" "20170901" "20170902" "20170903" "20170904" "20170905"
 
#Extract missing data in http://files.airnowtech.org/ if possible
extract_missingD <- function(missingD) {
  
  df<-data.frame()
  
  for (i in 1:length(missingD)) {
    
  df_sub <- read.delim(paste0("https://s3-us-west-1.amazonaws.com//files.airnowtech.org/airnow/",substr(missingD[i],1,4),"/",missingD[i],"/daily_data.dat"), sep="|", header=FALSE, col.names = fieldnames) %>% filter(AQSID %in% unique(MS$AQSID)) %>% filter(parameter.name=='OZONE-8HR' | parameter.name=='PM2.5-24hr') #Extract data and filter to only have the wanted MS and parameters (PM2.5/O3)
   
  df <- rbind(df,df_sub)
  }
  
  return(df)
}
#Extract missing data
missingD.data <- extract_missingD(missingD)
#Rearrange the results according to the wrangling process
missingD.data <- data_wrangling(missingD.data)
#Check if the daily_data.dat is daily peak values by using the same function to extract known informations
missingD.test <- sample_n(data, 10) #Take 10 random rows of the initial dataframe
missingD.test.data <- extract_missingD(format(missingD.test$date,"%Y%m%d")) #Extract values on files.airnowtech.org
missingD.test.data<- data_wrangling(missingD.test.data) #Reshape the resulting dataframe
missingD.comp<-inner_join(missingD.test,missingD.test.data,by=c('date','AQSID'))
#print(missingD.comp$O3.x==missingD.comp$O3.y)
#print(missingD.comp$PM25.x==missingD.comp$PM25.y)
#daily_data.dat corresponds to daily peak values
#Bind retrieved missing values to dataframe
data <- data %>% drop_na(AQSID) #Remove the rows added to detect missing values
data <- rbind(data,missingD.data)
#Number of missing values still missing
print(nrow(complete(data,date = seq.Date(min(date), max(date), by="day")))-nrow(data))
[1] 0
#Number of observations we're supposed to have for a complete time series (nb of days between the first and last day as daily measurements)
nbDays<-as.numeric(max(data$date) - min(data$date))

3 cases : - No data - Only seasonal data - Complete time series (more or less)

#EXPLORATORY DATA ANALYSIS of Ozone
MS.O3 <- filter(MS,parameter.name=='O3') %>% select(-parameter.name) #monitoring stations measuring O3
data.O3 <- data %>% filter(AQSID %in% MS.O3[['AQSID']]) %>% subset(select = -PM25) #subset of the dataframe keep only the MS recording O3 (the ones in MS.O3)
data.O3 <- data.O3 %>% droplevels() %>% complete(date,AQSID) #complete the dataframe with the missing combinaisons (data,AQSID) of data in order to make explicite the missing values
MS.O3 <- data.O3 %>% group_by(AQSID) %>% summarize(nb_na = sum(is.na(O3)), mean_val = mean(O3, na.rm=TRUE)) %>% full_join(MS.O3, by='AQSID') #count the nb of missing observations (obs=dailyPeak record) per node
data.O3 %>% ggplot(aes(x=date, y=O3)) + geom_line() + facet_wrap(~AQSID, nrow=5) + ggtitle('2013-2018 time series for the O3 measurements at each node') + labs(x='Date',y='O3 [ppb]') %>% print() #time series for each O3 monitoring station
$x
[1] "Date"

$y
[1] "O3 [ppb]"

attr(,"class")
[1] "labels"

MS.O3 <- MS.O3 %>% mutate(group=ifelse(nb_na < 0.1*nbDays, '>90%', ifelse(nb_na > 0.8*nbDays, '<20%', '20%-90%'))) #create groups according the different type of data frequency as noticed in the time series diagnostic
pal.typeData <- c('<20%'='#f66253','>90%'='#86b24f','20%-90%'='#f6a760') #create a color palette corresponding to these groups
print(MS.O3)
  
# ggplot() + geom_sf(data=counties) + geom_sf(data=MS.O3,aes(color=group)) + scale_colour_manual(values = pal.typeData,na.value='black') + ggtitle('O3 monitoring stations') + labs(colour='Time series completeness') + theme_void() #plot the O3 monitoring stations according the data frequency they have
write.csv(data.O3,'Data/data_O3.csv')
#EXPLORATORY DATA ANALYSIS of PM2.5
MS.PM25 <- filter(MS,parameter.name=='PM2.5') %>% select(-parameter.name) #monitoring stations measuring PM2.5
data.PM25 <- data %>% filter(AQSID %in% MS.PM25[['AQSID']]) %>% subset(select = -O3) #subset of the dataframe keep only the MS recording PM2.5 (the ones in MS.PM25)
data.PM25 <- data.PM25 %>% droplevels() %>% complete(date,AQSID) #complete the dataframe with the missing combinaisons (data,AQSID) of data in order to make explicite the missing values
MS.PM25 <- data.PM25 %>% group_by(AQSID) %>% summarize(nb_na = sum(is.na(PM25))) %>% full_join(MS.PM25, by='AQSID') #count the nb of missing observations (obs=dailyPeak record) per node
print(MS.PM25)
data.PM25 %>% ggplot(aes(x=date, y=PM25)) + geom_line() + facet_wrap(~AQSID, nrow=4) + ggtitle('2013-2018 time series for the PM2.5 measurements at each node') + labs(x='Date',y='PM2.5 [ug/m3]') %>% print() #time series for each PM2.5 monitoring station
$x
[1] "Date"

$y
[1] "PM2.5 [ug/m3]"

attr(,"class")
[1] "labels"

MS.PM25 <- MS.PM25 %>% mutate(group=ifelse(nb_na < 0.1*nbDays, '>90%', ifelse(nb_na > 0.8*nbDays, '<20%', '20%-90%'))) #create groups according the different type of data frequency as noticed in the time series diagnostic
print(MS.PM25)
# ggplot() + geom_sf(data=counties) + geom_sf(data=MS.PM25,aes(color=group)) + scale_colour_manual(values = pal.typeData,na.value='black') + ggtitle('PM2.5 monitoring stations') + labs(colour='Time series completeness') + theme_void() #plot the O3 monitoring stations according the data frequency they have
write.csv(data.PM25,'Data/data_PM25.csv')
tmap_mode("view")
tmap mode set to interactive viewing
popup.vars <- c("AQSID"="AQSID","Site Name"="site.name","State"="state.name","County"="county.name","Primary Objective"="primary.objective","Secondary Objective"="secondary.objective","Spatial Scale"="spatial.scale","Station Type"="station.type")
tm_shape(counties) + tm_borders(col='black', lty=2) +
  tm_shape(st_sf(MS.O3), name='O3 monitoring stations') + tm_dots(col='group',palette=pal.typeData,popup.vars=popup.vars) +
  tm_shape(st_sf(MS.PM25), name='PM2.5 monitoring stations') + tm_dots(col='group',palette=pal.typeData,legend.show=FALSE,popup.vars=popup.vars) + tm_layout(title='Select MS for a specific pollutant in the layer selection')

annotate(“text”, x = -80, y = 35, label=MS.PM25$AQSID[1]) +

What I want

-Forecast of the ambient air pollution (O3, PM2.5) 24h ahead of time -Spatial predictions at unmonitored locations to have a continuous field of O3-PM2.5 predictions for the next day -Mapping of the new AQI -Potential improvement : The Array of Things project

Data at ~15 locations for PM and ~15 locations for O3 and data from 2013 to present.

Use of Ozone-8h measurements and PM2.5-24hr measurements (to allow direct comparisons with the standards values). PM2.5 : 35ug/m3 is the 24-hours standard and 15ug/m3 is the annual standard O3 : 0.075 ppm is the 8-hours standard

Pay attention : -temporal autocorrelation to find the time period to use? -counties to include in the study that can influence the air pollution in the Chicago area? -anisotropic field due to the lake effect -space/time separable or non-separable process? -Bayesian Spatial Predictor (separate spatio-temporal process) or temporal model (ex : SVM) and after co-kriging ?

In the case of BMS, need to split dataset to ungauged and gauged sites so lost of informations.

LS0tCnRpdGxlOiAiQWlyTm93IgphdXRob3IgOiAiQW5hw69zIExhZG95IgpkYXRlIDogIjI3LjA0LjIwMTgiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgZmlnX2NhcHRpb246IHllcwotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKCgpgYGB7ciwgcmVzdWx0cz1GQUxTRX0KIyBMSUJSQVJJRVMKCmxpYnJhcnkodGlkeXZlcnNlKSAjU2V0IG9mIHBhY2thZ2VzIHRoYXQgd29yayBpbiBoYXJtb255IGZvciBkYXRhIHJlcHJlc2VudGF0aW9ucyBhbmQgQVBJIGRlc2lnbiAoZ2dwbG90MiwgZHBseXIsIHJlYWRyLCBldGMpCgpsaWJyYXJ5KHNmKSAjUGFja2FnZSBmb3IgU2ltcGxlIEZlYXR1cmVzLCB3YXkgdG8gZW5jb2RlIHNwYXRpYWwgdmVjdG9yIGRhdGEuIEJpbmRzIHRvIEdEQUwgZm9yIHJlYWRpbmcvd3JpdGluZyBvZiBkYXRhLCBHRU9TIGZvciBnZW9tZXRyaWNhbCBvcGVyYXRpb25zIGFuZCBQUk9KIGZvciBwcm9qZWN0aW9uIGNvbnZlcnNpb25zL2RhdHVtIHRyYW5zZm9ybWF0aW9ucyAoaHR0cDovL3N0cmltYXMuY29tL3IvdGlkeS1zZi8pCgpsaWJyYXJ5KHRtYXApCgpsaWJyYXJ5KFVTQWJvdW5kYXJpZXMpICNCb3VuZGFyaWVzIGZvciBnZW9ncmFwaGljYWwgdW5pdHMgaW4gdGhlIFVuaXRlZCBTdGF0ZXMgb2YgQW1lcmljYSAoVS5TLiBDZW5zdXMgQnVyZWF1KQoKbGlicmFyeSh0aWR5cikgI1VzZSB0byByZXNoYXBlIGRhdGFmcmFtZQoKbGlicmFyeShsdWJyaWRhdGUpICNIYW5kbGUgZGF0ZXMvdGltZSBzZXJpZXMKCmxpYnJhcnkoZm9yY2F0cykgI0hhbmRsZSBmYWN0b3JzCgpgYGAKCgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIyBNT05JVE9SSU5HIFNJVEVTIExPQ0FUSU9OUwoKI0Fic29sdXRlIHBhdGggb2YgdGhlIE1TIGxvY2F0aW9ucyBpbiB0aGUgRlRQIHNlcnZlcgpsb2NmaWxlIDwtICdEYXRhL0Fpck5vdy9tb25pdG9yaW5nX3NpdGVfbG9jYXRpb25zLmRhdCcKCiNNZXRhZGF0YSBvbiBNb25pdG9yaW5nU2l0ZUZhY3RTaGVldC5wZGYKZmllbGRuYW1lcyA8LSBjKCJBUVNJRCIsInBhcmFtZXRlci5uYW1lIiwic2l0ZS5jb2RlIiwic2l0ZS5uYW1lIiwic3RhdHVzIiwiYWdlbmN5LmlkIiwiYWdlbmN5Lm5hbWUiLCJFUEEucmVnaW9uIiwibGF0aXR1ZGUiLCJsb25naXR1ZGUiLCJlbGV2YXRpb24iLCJHTVQub2Zmc2V0IiwiY291bnRyeS5jb2RlIiwiQ01TQS5jb2RlIiwiQ01TQS5uYW1lIiwiTVNBLmNvZGUiLCJNU0EubmFtZSIsInN0YXRlLmNvZGUiLCJzdGF0ZS5uYW1lIiwiY291bnR5LmNvZGUiLCJjb3VudHkubmFtZSIsImNpdHkuY29kZSIsImNpdHkubmFtZSIpCgojUmVhZCB0aGUgLmRhdCBmaWxlIGluIGEgdGliYmxlIGRhdGFmcmFtZS4gVGliYmxlcyBhcmUgbW9kZXJuIGRhdGEgZnJhbWVzIHRoYXQga2VlcCB0aGUgdXNlZnVsIGZlYXR1cmVzIG9mIHRoZSB0cmFkaXRpb25hbCBkYXRhIGZyYW1lcyBhbmQgZHJvcCBvbmVzIHRoYXQgd2VyZSBmcnVzdGF0aW5nIChlLmcuIGNoYXJhY3RlciB2ZWN0b3JzIHRvIGZhY3Rvciwgc2hvdyBvbmx5IGNvbHVtbnMgdGhhdCBmaXQgdG8gdGhlIHNjcmVlbiB3aGVuIHByaW50aW5nLCBldGMuKQpNUyA8LSByZWFkX2RlbGltKGxvY2ZpbGUsIGRlbGltPSd8JywgY29sX25hbWVzID0gZmllbGRuYW1lcywgcHJvZ3Jlc3MgPSBUUlVFKQpwcmludChNUykKCiNMaXN0IG9mIHBhcmFtZXRlcnMgbWVhc3VyZWQKTVMuY2hlbXMgPC0gdW5pcXVlKE1TJHBhcmFtZXRlci5uYW1lKQpwcmludChwYXN0ZSgnSW4gdG90YWwsIHRoZXJlIGFyZScsbGVuZ3RoKE1TLmNoZW1zKSwncGFyYW1ldGVycyBtZWFzdXJlZCcsc2VwPScgJykpCnByaW50KE1TLmNoZW1zKQoKI0xpc3Qgb2Ygc3RhdGlvbnMKTVMuc3RhdCA8LSB1bmlxdWUoTVMkQVFTSUQpCnByaW50KHBhc3RlKCdJbiB0b3RhbCwgdGhlcmUgYXJlJyxsZW5ndGgoTVMuc3RhdCksJ2RpZmZlcmVudCBzdGF0aW9ucycsc2VwPScgJykpCgpgYGAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojTVMgbG9jYXRpb25zCgojRXh0cmFjdCBzdGF0aW9ucyBtZWFzdXJpbmcgb3pvbmUgKE1TLmNoZW1zPU8zKSBhbmQgcGFydGljdWxhdGUgbWF0dGVyIChNUy5jaGVtcz1QTTIuNSkKTVMgPC0gZmlsdGVyKE1TLHBhcmFtZXRlci5uYW1lICVpbiUgYygnUE0yLjUnLCdPMycpKQoKI0NvbnZlcnQgdGhlIGRhdGEgZnJhbWUgdG8gYSBzcGF0aWFsIGRhdGEgZnJhbWUgYW5kIHByb2plY3QgdGhlIGNvb3JkaW5hdGVzIGluIFVTQSBDb250aWd1b3VzIEFsYmVycyBFcXVhbCBBcmVhIENvbmljIChFUFNHOjEwMjAwMykKTVMgPC0gc3RfYXNfc2YoTVMsIGNvb3JkcyA9IGMoJ2xvbmdpdHVkZScsJ2xhdGl0dWRlJyksIGFnciA9ICdpZGVudGl0eScsIGNycz00MzI2KSAlPiUgc3RfdHJhbnNmb3JtKE1TLCBjcnM9MTAyMDAzKSAKCiNWaXN1YWxpc2F0aW9uIG9mIFBNMi41IGFuZCBPMyBNUyAoZGlmZmVyZW50IGNvbG9ycyBmb3IgYWN0aXZlL2luYWN0aXZlIE1TKQp0bWFwX21vZGUoInZpZXciKQp0bV9zaGFwZSh1c19jb3VudGllcygpKSArIHRtX2JvcmRlcnMoY29sPSdibGFjaycsIGx0eT0yKSArCiAgdG1fc2hhcGUodXNfc3RhdGVzKCkpICsgdG1fYm9yZGVycyhjb2w9J2JsYWNrJywgbHdkPTIpICsKICB0bV9zaGFwZShNU1tNUyRwYXJhbWV0ZXIubmFtZT09J1BNMi41JyxdLG5hbWU9J1BNMi41IE1TIGxvY2F0aW9ucycpICsgICAgICAgICAgdG1fZG90cyhjb2w9J3N0YXR1cycscGFsZXR0ZT1jKCdibHVlJywncmVkJykpICsgCiAgdG1fc2hhcGUoTVNbTVMkcGFyYW1ldGVyLm5hbWU9PSdPMycsXSxuYW1lPSdPMyBNUyBsb2NhdGlvbnMnKSArIHRtX2RvdHMoY29sPSdzdGF0dXMnLHBhbGV0dGU9YygnYmx1ZScsJ3JlZCcpLGxlZ2VuZC5zaG93PUZBTFNFKQoKYGBgCgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiNDUkVBVEUgVFdPIERBVEFGUkFNRVMgOiBjb3VudGllcyB0byBzdG9yZSBhbGwgdGhlIGNvdW50aWVzIHdlIHdhbnQgdG8ga2VlcCwgTVMgdG8gc3RvcmUgYWxsIHRoZSBtb25pdG9yaW5nIHN0YXRpb25zIGJlbG9uZ2luZyB0byB0aGUgY291bnRpZXMKCiNpbmNsdWRlIG5laWdoYm9yaW5nIGNvdW50aWVzIG9mIENoaWNhZ28gYW5kIGFsc28gTGFrZSBhbmQgUG9ydGVyIGZyb20gSW5kaWFuYSAobG90IG9mIGluZHVzdHJpZXMpCgpjb3VudGllcyA8LSB1c19jb3VudGllcygpICU+JSBmaWx0ZXIoc3RhdGVfbmFtZSAlaW4lIGMoJ0lsbGlub2lzJywnSW5kaWFuYScsJ1dpc2NvbnNpbicpICYgbmFtZSAlaW4lIGMoJ0Nvb2snLCdMYWtlJywnRHVQYWdlJywnV2lsbCcsJ1BvcnRlcicsJ0tlbm9zaGEnKSkgJT4lIHN0X3RyYW5zZm9ybShjb3VudGllcywgY3JzPTEwMjAwMykgCgojU2l0ZXMgbWVhc3VyaW5nIE8zIGFuZCBQTTIuNSBhdCB0aGUgNiBjb3VudGllcyB3ZSdyZSBpbnRlcmVzdGVkIGluCk1TIDwtIE1TW3doaWNoKHN0X2ludGVyc2VjdHMoTVMsY291bnRpZXMpID4gMCksXQoKI1NhbWUgTVMgZm9yIFBNMi41IGFuZCBPMwpNUy5ib3RoPC1ncm91cF9ieShNUyxieT1BUVNJRCkgJT4lIHN1bW1hcmlzZShsZW5ndGg9bigpKSAlPiUgZmlsdGVyKGxlbmd0aD09MikgJT4lIC5bWydieSddXQoKI1Bsb3QgdGhlIGNvdW50aWVzIGluY2x1ZGVkIGluIHRoZSBNUyBsb2NhdGlvbgpnZ3Bsb3QoY291bnRpZXMsYWVzKGZpbGwgPSBhZmZnZW9pZCkpICsgZ2VvbV9zZigpCgojQWRkIG1ldGFkYXRhIGFib3V0IHRoZSBNb25pdG9yaW5nIFN0YXRpb25zIChUeXBlLCBPYmplY3RpdmVzLCAuLi4pIGZyb20gdGhlIGRpZmZlcmVudCBzdGF0ZXMgZG9jdW1lbnRzIChJbGxpbm9pcywgSW5kaWFuYSwgV2lzY29uc2luKS4gSW5mb3JtYXRpb25zIGZvciBXaXNjb25zaW4gYW5kIEluZGlhbmEgd2lsbCBiZSBhZGRlZCBtYW51YWxseSAob25seSBhIGZldyBzdGF0aW9ucykKcGF0aF9kb2NzIDwtICcvVm9sdW1lcy9Hb29nbGVEcml2ZS9NeSBEcml2ZS9QRE0vRG9jcy8nCgpNUyA8LSByZWFkX2NzdihwYXN0ZTAocGF0aF9kb2NzLCdtZXRhX08zX01TLmNzdicpLGNvbF9uYW1lcyA9IFRSVUUsbmEgPSBjKCJOL0EiLCAiTkEiKSkgJT4lIHJiaW5kKHJlYWRfY3N2KHBhc3RlMChwYXRoX2RvY3MsJ21ldGFfUE0yNV9NUy5jc3YnKSxjb2xfbmFtZXMgPSBUUlVFLG5hID0gYygiTi9BIiwgIk5BIikpKSAlPiUgbXV0YXRlKEFRU0lEID0gZ3N1YihwYXR0ZXJuPSctJyxyZXBsYWNlbWVudCA9ICcnLHg9QVFTSUQpKSAlPiUgbWVyZ2UoeD1NUyxieT1jKCdBUVNJRCcsJ3BhcmFtZXRlci5uYW1lJyksYWxsLng9VFJVRSkKCk1TIDwtIE1TICU+JSBzZWxlY3QoQVFTSUQsc2l0ZS5uYW1lLngscGFyYW1ldGVyLm5hbWUsc3RhdHVzLHN0YXRlLm5hbWUsY291bnR5Lm5hbWUscHJpbWFyeS5vYmplY3RpdmUsc2Vjb25kYXJ5Lm9iamVjdGl2ZSxzcGF0aWFsLnNjYWxlLHN0YXRpb24udHlwZSkgJT4lIHJlbmFtZShzaXRlLm5hbWU9c2l0ZS5uYW1lLngpCgpgYGAKCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KI1JFQUQgREFUQSBGT1IgQUxMIFlFQVJTCgp5ZWFyZGlyIDwtICdEYXRhL0Fpck5vdy9EYWlseVBlYWsnCnllYXJmaWxlcyA8LSBsaXN0LmZpbGVzKHllYXJkaXIsZnVsbC5uYW1lcz1UUlVFKQpmaWVsZG5hbWVzIDwtIGMoInZkYXRlIiwiQVFTSUQiLCJzaXRlLm5hbWUiLCJwYXJhbWV0ZXIubmFtZSIsInJlcG9ydC51bml0cyIsCiAgICAgICAgICAgICAgICAidmFsdWUiLCJhdmcucGVyaW9kIiwiYWdlbmN5Lm5hbWUiKQoKI0Z1bmN0aW9uIHRvIGV4dHJhY3QgdGhlIGRlc2lyZWQgZGF0YSAoT3pvbmUtOGggYW5kIFBNMi41LTI0aCkgZm9yIGEgZ2l2ZW4gZmlsZQpleHRyYWN0X2RhdGEgPC0gZnVuY3Rpb24oZmlsZSkgewogIHJldHVybihyZWFkX2RlbGltKGZpbGUsICJ8IiwgY29sX25hbWVzID0gZmllbGRuYW1lcywgcHJvZ3Jlc3MgPSBGQUxTRSkgJT4lIGZpbHRlcihBUVNJRCAlaW4lIHVuaXF1ZShNUyRBUVNJRCkpICU+JSBmaWx0ZXIocGFyYW1ldGVyLm5hbWU9PSdPWk9ORS04SFInIHwgcGFyYW1ldGVyLm5hbWU9PSdQTTIuNS0yNGhyJykpCn0KCiNGb3IgbG9vcCB0byBnbyBvdmVyIGFsbCB0aGUgZmlsZXMgaW4gdGhlIGRpcmVjdG9yeQpkYXRhIDwtIGRhdGEuZnJhbWUoKQoKZm9yKGogaW4gMTpsZW5ndGgoeWVhcmZpbGVzKSkgewogIGRhdGFkaXIgPC0gcGFzdGUwKHllYXJkaXIsc3Vic3RyKHllYXJmaWxlc1tqXSxuY2hhcih5ZWFyZmlsZXNbal0pLTQsbmNoYXIoeWVhcmZpbGVzW2pdKSkpCiAgZGF0ZmlsZXMgPC0gbGlzdC5maWxlcyhkYXRhZGlyLGZ1bGwubmFtZXM9VFJVRSkKICAKICBmb3IoaSBpbiAxOmxlbmd0aChkYXRmaWxlcykpCiAgewogIHJlcyA8LSBleHRyYWN0X2RhdGEoZGF0ZmlsZXNbaV0pCiAgZGF0YSA8LSByYmluZChkYXRhLHJlcykKICB9Cn0KCnN0cihkYXRhKQpgYGAKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQojREFUQSBXUkFOR0xJTkcKZGF0YS5iY2twIDwtIGRhdGEKI2RhdGEgPC0gZGF0YS5iY2twCgpkYXRhX3dyYW5nbGluZyA8LSBmdW5jdGlvbihkZikgewoKICAjS2VlcCBvbmx5IHVzZWZ1bCBmaWVsZHMgKHRpbWUsIEFRU0lELCBwYXJhbWV0ZXIsIHZhbHVlcykKICBkZiA8LSBzZWxlY3QoZGYsYygndmRhdGUnLCdBUVNJRCcsJ3BhcmFtZXRlci5uYW1lJywndmFsdWUnKSkgCiAgCiAgI0NvbnZlcnQgdG8gdGlkeSBmb3JtYXQgKHNwcmVhZCBwYXJhbWV0ZXIgbmFtZSkKICBkZiA8LSBzcHJlYWQoZGYsIGtleT0ncGFyYW1ldGVyLm5hbWUnLCB2YWx1ZT0ndmFsdWUnKQogIAogICNDaGFuZ2UgbmFtZSBvZiBjb2x1bW5zCiAgbmFtZXMoZGYpIDwtIGMoJ2RhdGUnLCdBUVNJRCcsJ08zJywnUE0yNScpCiAgCiAgI0RhdGEgY29udmVyc2lvbgogIGRmJGRhdGU8LW1keShkZiRkYXRlKSAjZGF0ZSBmcm9tIGNoYXIgdG8gZGF0ZQogIGRmJEFRU0lEPC1mYWN0b3IoZGYkQVFTSUQpICNBUVNJRCBmcm9tIGNoYXIgdG8gZmFjdG9yCgogIHJldHVybihkZikKfQoKI0RhdGEgd3JhbmdsaW5nIHByb2Nlc3MKZGF0YSA8LSBkYXRhX3dyYW5nbGluZyhkYXRhKQoKI01ha2UgYXBwZWFyIG1pc3NpbmcgZGF0ZXMgZXhwbGljaXRlbHkgdXNpbmcgY29tcGxldGUoKSBmcm9tIHRpZHlyCmRhdGEgPC0gY29tcGxldGUoZGF0YSxkYXRlID0gc2VxLkRhdGUobWluKGRhdGUpLCBtYXgoZGF0ZSksIGJ5PSJkYXkiKSkKCiNQcmludCBpbXBsaWNpdCBtaXNzaW5nIHZhbHVlcwptaXNzaW5nRCA8LSBmaWx0ZXIoZGF0YSwgaXMubmEoTzMpICYgaXMubmEoUE0yNSkpICU+JSAuW1snZGF0ZSddXSAlPiUgZm9ybWF0KCIlWSVtJWQiKQpwcmludChtaXNzaW5nRCkgI0RhdGVzIHdpdGggbWlzc2luZyBkYXRhCiAKI0V4dHJhY3QgbWlzc2luZyBkYXRhIGluIGh0dHA6Ly9maWxlcy5haXJub3d0ZWNoLm9yZy8gaWYgcG9zc2libGUKZXh0cmFjdF9taXNzaW5nRCA8LSBmdW5jdGlvbihtaXNzaW5nRCkgewogIAogIGRmPC1kYXRhLmZyYW1lKCkKICAKICBmb3IgKGkgaW4gMTpsZW5ndGgobWlzc2luZ0QpKSB7CiAgICAKICBkZl9zdWIgPC0gcmVhZC5kZWxpbShwYXN0ZTAoImh0dHBzOi8vczMtdXMtd2VzdC0xLmFtYXpvbmF3cy5jb20vL2ZpbGVzLmFpcm5vd3RlY2gub3JnL2Fpcm5vdy8iLHN1YnN0cihtaXNzaW5nRFtpXSwxLDQpLCIvIixtaXNzaW5nRFtpXSwiL2RhaWx5X2RhdGEuZGF0IiksIHNlcD0ifCIsIGhlYWRlcj1GQUxTRSwgY29sLm5hbWVzID0gZmllbGRuYW1lcykgJT4lIGZpbHRlcihBUVNJRCAlaW4lIHVuaXF1ZShNUyRBUVNJRCkpICU+JSBmaWx0ZXIocGFyYW1ldGVyLm5hbWU9PSdPWk9ORS04SFInIHwgcGFyYW1ldGVyLm5hbWU9PSdQTTIuNS0yNGhyJykgI0V4dHJhY3QgZGF0YSBhbmQgZmlsdGVyIHRvIG9ubHkgaGF2ZSB0aGUgd2FudGVkIE1TIGFuZCBwYXJhbWV0ZXJzIChQTTIuNS9PMykKICAgCiAgZGYgPC0gcmJpbmQoZGYsZGZfc3ViKQogIH0KICAKICByZXR1cm4oZGYpCn0KCiNFeHRyYWN0IG1pc3NpbmcgZGF0YQptaXNzaW5nRC5kYXRhIDwtIGV4dHJhY3RfbWlzc2luZ0QobWlzc2luZ0QpCgojUmVhcnJhbmdlIHRoZSByZXN1bHRzIGFjY29yZGluZyB0byB0aGUgd3JhbmdsaW5nIHByb2Nlc3MKbWlzc2luZ0QuZGF0YSA8LSBkYXRhX3dyYW5nbGluZyhtaXNzaW5nRC5kYXRhKQoKI0NoZWNrIGlmIHRoZSBkYWlseV9kYXRhLmRhdCBpcyBkYWlseSBwZWFrIHZhbHVlcyBieSB1c2luZyB0aGUgc2FtZSBmdW5jdGlvbiB0byBleHRyYWN0IGtub3duIGluZm9ybWF0aW9ucwptaXNzaW5nRC50ZXN0IDwtIHNhbXBsZV9uKGRhdGEsIDEwKSAjVGFrZSAxMCByYW5kb20gcm93cyBvZiB0aGUgaW5pdGlhbCBkYXRhZnJhbWUKbWlzc2luZ0QudGVzdC5kYXRhIDwtIGV4dHJhY3RfbWlzc2luZ0QoZm9ybWF0KG1pc3NpbmdELnRlc3QkZGF0ZSwiJVklbSVkIikpICNFeHRyYWN0IHZhbHVlcyBvbiBmaWxlcy5haXJub3d0ZWNoLm9yZwptaXNzaW5nRC50ZXN0LmRhdGE8LSBkYXRhX3dyYW5nbGluZyhtaXNzaW5nRC50ZXN0LmRhdGEpICNSZXNoYXBlIHRoZSByZXN1bHRpbmcgZGF0YWZyYW1lCgptaXNzaW5nRC5jb21wPC1pbm5lcl9qb2luKG1pc3NpbmdELnRlc3QsbWlzc2luZ0QudGVzdC5kYXRhLGJ5PWMoJ2RhdGUnLCdBUVNJRCcpKQojcHJpbnQobWlzc2luZ0QuY29tcCRPMy54PT1taXNzaW5nRC5jb21wJE8zLnkpCiNwcmludChtaXNzaW5nRC5jb21wJFBNMjUueD09bWlzc2luZ0QuY29tcCRQTTI1LnkpCiNkYWlseV9kYXRhLmRhdCBjb3JyZXNwb25kcyB0byBkYWlseSBwZWFrIHZhbHVlcwoKI0JpbmQgcmV0cmlldmVkIG1pc3NpbmcgdmFsdWVzIHRvIGRhdGFmcmFtZQpkYXRhIDwtIGRhdGEgJT4lIGRyb3BfbmEoQVFTSUQpICNSZW1vdmUgdGhlIHJvd3MgYWRkZWQgdG8gZGV0ZWN0IG1pc3NpbmcgdmFsdWVzCmRhdGEgPC0gcmJpbmQoZGF0YSxtaXNzaW5nRC5kYXRhKQoKI051bWJlciBvZiBtaXNzaW5nIHZhbHVlcyBzdGlsbCBtaXNzaW5nCnByaW50KG5yb3coY29tcGxldGUoZGF0YSxkYXRlID0gc2VxLkRhdGUobWluKGRhdGUpLCBtYXgoZGF0ZSksIGJ5PSJkYXkiKSkpLW5yb3coZGF0YSkpCgojTnVtYmVyIG9mIG9ic2VydmF0aW9ucyB3ZSdyZSBzdXBwb3NlZCB0byBoYXZlIGZvciBhIGNvbXBsZXRlIHRpbWUgc2VyaWVzIChuYiBvZiBkYXlzIGJldHdlZW4gdGhlIGZpcnN0IGFuZCBsYXN0IGRheSBhcyBkYWlseSBtZWFzdXJlbWVudHMpCm5iRGF5czwtYXMubnVtZXJpYyhtYXgoZGF0YSRkYXRlKSAtIG1pbihkYXRhJGRhdGUpKQoKYGBgCgozIGNhc2VzIDoKLSBObyBkYXRhCi0gT25seSBzZWFzb25hbCBkYXRhCi0gQ29tcGxldGUgdGltZSBzZXJpZXMgKG1vcmUgb3IgbGVzcykKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQojRVhQTE9SQVRPUlkgREFUQSBBTkFMWVNJUyBvZiBPem9uZQoKTVMuTzMgPC0gZmlsdGVyKE1TLHBhcmFtZXRlci5uYW1lPT0nTzMnKSAlPiUgc2VsZWN0KC1wYXJhbWV0ZXIubmFtZSkgI21vbml0b3Jpbmcgc3RhdGlvbnMgbWVhc3VyaW5nIE8zCmRhdGEuTzMgPC0gZGF0YSAlPiUgZmlsdGVyKEFRU0lEICVpbiUgTVMuTzNbWydBUVNJRCddXSkgJT4lIHN1YnNldChzZWxlY3QgPSAtUE0yNSkgI3N1YnNldCBvZiB0aGUgZGF0YWZyYW1lIGtlZXAgb25seSB0aGUgTVMgcmVjb3JkaW5nIE8zICh0aGUgb25lcyBpbiBNUy5PMykKCmRhdGEuTzMgPC0gZGF0YS5PMyAlPiUgZHJvcGxldmVscygpICU+JSBjb21wbGV0ZShkYXRlLEFRU0lEKSAjY29tcGxldGUgdGhlIGRhdGFmcmFtZSB3aXRoIHRoZSBtaXNzaW5nIGNvbWJpbmFpc29ucyAoZGF0YSxBUVNJRCkgb2YgZGF0YSBpbiBvcmRlciB0byBtYWtlIGV4cGxpY2l0ZSB0aGUgbWlzc2luZyB2YWx1ZXMKTVMuTzMgPC0gZGF0YS5PMyAlPiUgZ3JvdXBfYnkoQVFTSUQpICU+JSBzdW1tYXJpemUobmJfbmEgPSBzdW0oaXMubmEoTzMpKSwgbWVhbl92YWwgPSBtZWFuKE8zLCBuYS5ybT1UUlVFKSkgJT4lIGZ1bGxfam9pbihNUy5PMywgYnk9J0FRU0lEJykgI2NvdW50IHRoZSBuYiBvZiBtaXNzaW5nIG9ic2VydmF0aW9ucyAob2JzPWRhaWx5UGVhayByZWNvcmQpIHBlciBub2RlCgpkYXRhLk8zICU+JSBnZ3Bsb3QoYWVzKHg9ZGF0ZSwgeT1PMykpICsgZ2VvbV9saW5lKCkgKyBmYWNldF93cmFwKH5BUVNJRCwgbnJvdz01KSArIGdndGl0bGUoJzIwMTMtMjAxOCB0aW1lIHNlcmllcyBmb3IgdGhlIE8zIG1lYXN1cmVtZW50cyBhdCBlYWNoIG5vZGUnKSArIGxhYnMoeD0nRGF0ZScseT0nTzMgW3BwYl0nKSAlPiUgcHJpbnQoKSAjdGltZSBzZXJpZXMgZm9yIGVhY2ggTzMgbW9uaXRvcmluZyBzdGF0aW9uCgpNUy5PMyA8LSBNUy5PMyAlPiUgbXV0YXRlKGdyb3VwPWlmZWxzZShuYl9uYSA8IDAuMSpuYkRheXMsICc+OTAlJywgaWZlbHNlKG5iX25hID4gMC44Km5iRGF5cywgJzwyMCUnLCAnMjAlLTkwJScpKSkgI2NyZWF0ZSBncm91cHMgYWNjb3JkaW5nIHRoZSBkaWZmZXJlbnQgdHlwZSBvZiBkYXRhIGZyZXF1ZW5jeSBhcyBub3RpY2VkIGluIHRoZSB0aW1lIHNlcmllcyBkaWFnbm9zdGljCnBhbC50eXBlRGF0YSA8LSBjKCc8MjAlJz0nI2Y2NjI1MycsJz45MCUnPScjODZiMjRmJywnMjAlLTkwJSc9JyNmNmE3NjAnKSAjY3JlYXRlIGEgY29sb3IgcGFsZXR0ZSBjb3JyZXNwb25kaW5nIHRvIHRoZXNlIGdyb3VwcwoKcHJpbnQoTVMuTzMpCiAgCiMgZ2dwbG90KCkgKyBnZW9tX3NmKGRhdGE9Y291bnRpZXMpICsgZ2VvbV9zZihkYXRhPU1TLk8zLGFlcyhjb2xvcj1ncm91cCkpICsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBwYWwudHlwZURhdGEsbmEudmFsdWU9J2JsYWNrJykgKyBnZ3RpdGxlKCdPMyBtb25pdG9yaW5nIHN0YXRpb25zJykgKyBsYWJzKGNvbG91cj0nVGltZSBzZXJpZXMgY29tcGxldGVuZXNzJykgKyB0aGVtZV92b2lkKCkgI3Bsb3QgdGhlIE8zIG1vbml0b3Jpbmcgc3RhdGlvbnMgYWNjb3JkaW5nIHRoZSBkYXRhIGZyZXF1ZW5jeSB0aGV5IGhhdmUKCndyaXRlLmNzdihkYXRhLk8zLCdEYXRhL2RhdGFfTzMuY3N2JykKCmBgYAoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiNFWFBMT1JBVE9SWSBEQVRBIEFOQUxZU0lTIG9mIFBNMi41CgpNUy5QTTI1IDwtIGZpbHRlcihNUyxwYXJhbWV0ZXIubmFtZT09J1BNMi41JykgJT4lIHNlbGVjdCgtcGFyYW1ldGVyLm5hbWUpICNtb25pdG9yaW5nIHN0YXRpb25zIG1lYXN1cmluZyBQTTIuNQpkYXRhLlBNMjUgPC0gZGF0YSAlPiUgZmlsdGVyKEFRU0lEICVpbiUgTVMuUE0yNVtbJ0FRU0lEJ11dKSAlPiUgc3Vic2V0KHNlbGVjdCA9IC1PMykgI3N1YnNldCBvZiB0aGUgZGF0YWZyYW1lIGtlZXAgb25seSB0aGUgTVMgcmVjb3JkaW5nIFBNMi41ICh0aGUgb25lcyBpbiBNUy5QTTI1KQoKZGF0YS5QTTI1IDwtIGRhdGEuUE0yNSAlPiUgZHJvcGxldmVscygpICU+JSBjb21wbGV0ZShkYXRlLEFRU0lEKSAjY29tcGxldGUgdGhlIGRhdGFmcmFtZSB3aXRoIHRoZSBtaXNzaW5nIGNvbWJpbmFpc29ucyAoZGF0YSxBUVNJRCkgb2YgZGF0YSBpbiBvcmRlciB0byBtYWtlIGV4cGxpY2l0ZSB0aGUgbWlzc2luZyB2YWx1ZXMKTVMuUE0yNSA8LSBkYXRhLlBNMjUgJT4lIGdyb3VwX2J5KEFRU0lEKSAlPiUgc3VtbWFyaXplKG5iX25hID0gc3VtKGlzLm5hKFBNMjUpKSkgJT4lIGZ1bGxfam9pbihNUy5QTTI1LCBieT0nQVFTSUQnKSAjY291bnQgdGhlIG5iIG9mIG1pc3Npbmcgb2JzZXJ2YXRpb25zIChvYnM9ZGFpbHlQZWFrIHJlY29yZCkgcGVyIG5vZGUKCnByaW50KE1TLlBNMjUpCgpkYXRhLlBNMjUgJT4lIGdncGxvdChhZXMoeD1kYXRlLCB5PVBNMjUpKSArIGdlb21fbGluZSgpICsgZmFjZXRfd3JhcCh+QVFTSUQsIG5yb3c9NCkgKyBnZ3RpdGxlKCcyMDEzLTIwMTggdGltZSBzZXJpZXMgZm9yIHRoZSBQTTIuNSBtZWFzdXJlbWVudHMgYXQgZWFjaCBub2RlJykgKyBsYWJzKHg9J0RhdGUnLHk9J1BNMi41IFt1Zy9tM10nKSAlPiUgcHJpbnQoKSAjdGltZSBzZXJpZXMgZm9yIGVhY2ggUE0yLjUgbW9uaXRvcmluZyBzdGF0aW9uCgpNUy5QTTI1IDwtIE1TLlBNMjUgJT4lIG11dGF0ZShncm91cD1pZmVsc2UobmJfbmEgPCAwLjEqbmJEYXlzLCAnPjkwJScsIGlmZWxzZShuYl9uYSA+IDAuOCpuYkRheXMsICc8MjAlJywgJzIwJS05MCUnKSkpICNjcmVhdGUgZ3JvdXBzIGFjY29yZGluZyB0aGUgZGlmZmVyZW50IHR5cGUgb2YgZGF0YSBmcmVxdWVuY3kgYXMgbm90aWNlZCBpbiB0aGUgdGltZSBzZXJpZXMgZGlhZ25vc3RpYwoKcHJpbnQoTVMuUE0yNSkKCiMgZ2dwbG90KCkgKyBnZW9tX3NmKGRhdGE9Y291bnRpZXMpICsgZ2VvbV9zZihkYXRhPU1TLlBNMjUsYWVzKGNvbG9yPWdyb3VwKSkgKyBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IHBhbC50eXBlRGF0YSxuYS52YWx1ZT0nYmxhY2snKSArIGdndGl0bGUoJ1BNMi41IG1vbml0b3Jpbmcgc3RhdGlvbnMnKSArIGxhYnMoY29sb3VyPSdUaW1lIHNlcmllcyBjb21wbGV0ZW5lc3MnKSArIHRoZW1lX3ZvaWQoKSAjcGxvdCB0aGUgTzMgbW9uaXRvcmluZyBzdGF0aW9ucyBhY2NvcmRpbmcgdGhlIGRhdGEgZnJlcXVlbmN5IHRoZXkgaGF2ZQoKd3JpdGUuY3N2KGRhdGEuUE0yNSwnRGF0YS9kYXRhX1BNMjUuY3N2JykKCmBgYAoKCmBgYHtyfQp0bWFwX21vZGUoInZpZXciKQpwb3B1cC52YXJzIDwtIGMoIkFRU0lEIj0iQVFTSUQiLCJTaXRlIE5hbWUiPSJzaXRlLm5hbWUiLCJTdGF0ZSI9InN0YXRlLm5hbWUiLCJDb3VudHkiPSJjb3VudHkubmFtZSIsIlByaW1hcnkgT2JqZWN0aXZlIj0icHJpbWFyeS5vYmplY3RpdmUiLCJTZWNvbmRhcnkgT2JqZWN0aXZlIj0ic2Vjb25kYXJ5Lm9iamVjdGl2ZSIsIlNwYXRpYWwgU2NhbGUiPSJzcGF0aWFsLnNjYWxlIiwiU3RhdGlvbiBUeXBlIj0ic3RhdGlvbi50eXBlIikKCnRtX3NoYXBlKGNvdW50aWVzKSArIHRtX2JvcmRlcnMoY29sPSdibGFjaycsIGx0eT0yKSArCiAgdG1fc2hhcGUoc3Rfc2YoTVMuTzMpLCBuYW1lPSdPMyBtb25pdG9yaW5nIHN0YXRpb25zJykgKyB0bV9kb3RzKGNvbD0nZ3JvdXAnLHBhbGV0dGU9cGFsLnR5cGVEYXRhLHBvcHVwLnZhcnM9cG9wdXAudmFycykgKwogIHRtX3NoYXBlKHN0X3NmKE1TLlBNMjUpLCBuYW1lPSdQTTIuNSBtb25pdG9yaW5nIHN0YXRpb25zJykgKyB0bV9kb3RzKGNvbD0nZ3JvdXAnLHBhbGV0dGU9cGFsLnR5cGVEYXRhLGxlZ2VuZC5zaG93PUZBTFNFLHBvcHVwLnZhcnM9cG9wdXAudmFycykgKyB0bV9sYXlvdXQodGl0bGU9J1NlbGVjdCBNUyBmb3IgYSBzcGVjaWZpYyBwb2xsdXRhbnQgaW4gdGhlIGxheWVyIHNlbGVjdGlvbicpCgpgYGAKCgoKYW5ub3RhdGUoInRleHQiLCB4ID0gLTgwLCB5ID0gMzUsIGxhYmVsPU1TLlBNMjUkQVFTSURbMV0pICsKCiMjV2hhdCBJIHdhbnQKLUZvcmVjYXN0IG9mIHRoZSBhbWJpZW50IGFpciBwb2xsdXRpb24gKE8zLCBQTTIuNSkgMjRoIGFoZWFkIG9mIHRpbWUKLVNwYXRpYWwgcHJlZGljdGlvbnMgYXQgdW5tb25pdG9yZWQgbG9jYXRpb25zIHRvIGhhdmUgYSBjb250aW51b3VzIGZpZWxkIG9mIE8zLVBNMi41IHByZWRpY3Rpb25zIGZvciB0aGUgbmV4dCBkYXkKLU1hcHBpbmcgb2YgdGhlIG5ldyBBUUkKLVBvdGVudGlhbCBpbXByb3ZlbWVudCA6IFRoZSBBcnJheSBvZiBUaGluZ3MgcHJvamVjdAoKRGF0YSBhdCB+MTUgbG9jYXRpb25zIGZvciBQTSBhbmQgfjE1IGxvY2F0aW9ucyBmb3IgTzMgYW5kIGRhdGEgZnJvbSAyMDEzIHRvIHByZXNlbnQuCgpVc2Ugb2YgT3pvbmUtOGggbWVhc3VyZW1lbnRzIGFuZCBQTTIuNS0yNGhyIG1lYXN1cmVtZW50cyAodG8gYWxsb3cgZGlyZWN0IGNvbXBhcmlzb25zIHdpdGggdGhlIHN0YW5kYXJkcyB2YWx1ZXMpLgpQTTIuNSA6IDM1dWcvbTMgaXMgdGhlIDI0LWhvdXJzIHN0YW5kYXJkIGFuZCAxNXVnL20zIGlzIHRoZSBhbm51YWwgc3RhbmRhcmQKTzMgOiAwLjA3NSBwcG0gaXMgdGhlIDgtaG91cnMgc3RhbmRhcmQKClBheSBhdHRlbnRpb24gOgotdGVtcG9yYWwgYXV0b2NvcnJlbGF0aW9uIHRvIGZpbmQgdGhlIHRpbWUgcGVyaW9kIHRvIHVzZT8KLWNvdW50aWVzIHRvIGluY2x1ZGUgaW4gdGhlIHN0dWR5IHRoYXQgY2FuIGluZmx1ZW5jZSB0aGUgYWlyIHBvbGx1dGlvbiBpbiB0aGUgQ2hpY2FnbyBhcmVhPwotYW5pc290cm9waWMgZmllbGQgZHVlIHRvIHRoZSBsYWtlIGVmZmVjdAotc3BhY2UvdGltZSBzZXBhcmFibGUgb3Igbm9uLXNlcGFyYWJsZSBwcm9jZXNzPwotQmF5ZXNpYW4gU3BhdGlhbCBQcmVkaWN0b3IgKHNlcGFyYXRlIHNwYXRpby10ZW1wb3JhbCBwcm9jZXNzKSBvciB0ZW1wb3JhbCBtb2RlbCAoZXggOiBTVk0pIGFuZCBhZnRlciBjby1rcmlnaW5nID8KCkluIHRoZSBjYXNlIG9mIEJNUywgbmVlZCB0byBzcGxpdCBkYXRhc2V0IHRvIHVuZ2F1Z2VkIGFuZCBnYXVnZWQgc2l0ZXMgc28gbG9zdCBvZiBpbmZvcm1hdGlvbnMuCgo=